/*
 * GL_Window.cpp
 */

#include "GL_Window.h"

GL_Window::GL_Window(char *className, HINSTANCE hInstance)
{
	this->className					= className;
	this->hInstance					= hInstance;

	this->arbMultisampleFormat		= 0;
	this->arbMultisampleSupported	= false;
	this->firstRun					= true;

	this->isRunning					= true;
	this->terminate 				= false;

	this->title				= "Basic GL window";
	this->width				= 1024;										// Window width
	this->height			= 768;										// Window height
	this->bitsPerPixel		= 32;										// Bits per pixel
	this->fullScreen 		= true;										// Fullscreen? (Default to true)
	this->multisample		= true;										// Fulscreen AA

}

GL_Window::~GL_Window()
{
	destroyWindowGL();
	UnregisterClass (className, hInstance);
}

bool GL_Window::registerWindowClass(WNDPROC windowProc)
{
	WNDCLASSEX windowClass;												// Window class
	ZeroMemory (&windowClass, sizeof (WNDCLASSEX));						// Make sure memory is cleared
	windowClass.cbSize			= sizeof (WNDCLASSEX);					// Size of the windowClass structure
	windowClass.style			= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;	// Redraws the window for any movement / resizing
	windowClass.lpfnWndProc		= (WNDPROC)(windowProc);				// WindowProc handles messages
	windowClass.hInstance		= hInstance;							// Set the instance
	windowClass.hbrBackground	= (HBRUSH)(COLOR_APPWORKSPACE);			// Class background brush color
	windowClass.hCursor			= LoadCursor(NULL, IDC_ARROW);			// Load the arrow pointer
	windowClass.lpszClassName	= className;							// Sets the applications classname

	if (RegisterClassEx (&windowClass) == 0)							// Did registering the class fail?
		return false;
	else
		return true;
}

/* Reshape the window when it's moved or resized */
void GL_Window::reshapeGL(int width, int height)
{
	glViewport (0, 0, (GLsizei)(width), (GLsizei)(height));				// Reset the current viewport
	glMatrixMode (GL_PROJECTION);										// Select the projection matrix
	glLoadIdentity ();													// Reset the projection matrix
	gluPerspective(50, (float)width/(float)height, 0.1f,  2000.0f);
	glMatrixMode (GL_MODELVIEW);										// Select the modelview matrix
	glLoadIdentity ();													// Reset the modelview matrix
}

/* Change The Screen Resolution */
bool GL_Window::changeScreenResolution(int width, int height, int bitsPerPixel)
{
	DEVMODE dmScreenSettings;											// Device mode
	ZeroMemory (&dmScreenSettings, sizeof (DEVMODE));					// Make sure memory is cleared
	dmScreenSettings.dmSize				= sizeof (DEVMODE);				// Size of the devmode structure
	dmScreenSettings.dmPelsWidth		= width;						// Select screen width
	dmScreenSettings.dmPelsHeight		= height;						// Select screen height
	dmScreenSettings.dmBitsPerPel		= bitsPerPixel;					// select bits per pixel
	dmScreenSettings.dmFields			= DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

	if (ChangeDisplaySettings (&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
		return false;
	else
		return true;
}

/* Destroy the OpenGL window & release resources */
bool GL_Window::destroyWindowGL()
{
	if (hWnd != 0) {							// Does the window have a handle?
		if (hDC != 0) {							// Does the window have a device context?
			wglMakeCurrent (hDC, 0);			// Set the current active rendering context to zero
			if (hRC != 0) {						// Does the window have a rendering context?
				wglDeleteContext (hRC);			// Release the rendering context
				hRC = 0;
			}
			ReleaseDC (hWnd, hDC);				// Release the device context
			hDC = 0;
		}
		DestroyWindow (hWnd);					// Destroy the window
		hWnd = 0;
	}

	/* revert to desktop resolution*/
	if (fullScreen) {
		ChangeDisplaySettings (NULL,0);
		ShowCursor (true);
	}

	return true;
}

bool GL_Window::toggleFullScreen()
{
	if (destroyWindowGL() == false)
		return false;

	fullScreen = (fullScreen == true) ? false : true;

	if (createWindowGL() == false)
		return false;

	return true;
}

bool GL_Window::toggleMultisample()
{
	if (destroyWindowGL() == false)
		return false;

	multisample = (multisample == true) ? false : true;

	if (createWindowGL() == false)
		return false;

	return true;
}

void GL_Window::swapBuffers()
{
	SwapBuffers (hDC);
}

bool GL_Window::WGLisExtensionSupported(const char *extension)
{
	const size_t extlen = strlen(extension);
	const char *supported = NULL;

	// Try To Use wglGetExtensionStringARB On Current DC, If Possible
	PROC wglGetExtString = wglGetProcAddress("wglGetExtensionsStringARB");

	if (wglGetExtString)
		supported = ((char*(__stdcall*)(HDC))wglGetExtString)(wglGetCurrentDC());

	// If That Failed, Try Standard Opengl Extensions String
	if (supported == NULL)
		supported = (char*)glGetString(GL_EXTENSIONS);

	// If That Failed Too, Must Be No Extensions Supported
	if (supported == NULL)
		return false;

	// Begin Examination At Start Of String, Increment By 1 On False Match
	for (const char* p = supported; ; p++)
	{
		// Advance p Up To The Next Possible Match
		p = strstr(p, extension);

		if (p == NULL)
			return false;															// No Match

		// Make Sure That Match Is At The Start Of The String Or That
		// The Previous Char Is A Space, Or Else We Could Accidentally
		// Match "wglFunkywglExtension" With "wglExtension"

		// Also, Make Sure That The Following Character Is Space Or NULL
		// Or Else "wglExtensionTwo" Might Match "wglExtension"
		if ((p==supported || p[-1]==' ') && (p[extlen]=='\0' || p[extlen]==' '))
			return true;															// Match
	}
}

bool GL_Window::initMultisample()
{
	 // See If The String Exists In WGL!
	if (!WGLisExtensionSupported("WGL_ARB_multisample"))
	{
		arbMultisampleSupported=false;
		return false;
	}

	// Get Our Pixel Format
	PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
	if (!wglChoosePixelFormatARB)
	{
		arbMultisampleSupported=false;
		return false;
	}

	// Get Our Current Device Context
	HDC hDC = GetDC(hWnd);

	int		pixelFormat;
	int		valid;
	UINT	numFormats;
	float	fAttributes[] = {0,0};

	// These Attributes Are The Bits We Want To Test For In Our Sample
	// Everything Is Pretty Standard, The Only One We Want To
	// Really Focus On Is The SAMPLE BUFFERS ARB And WGL SAMPLES
	// These Two Are Going To Do The Main Testing For Whether Or Not
	// We Support Multisampling On This Hardware.
	int iAttributes[] =
	{
		WGL_DRAW_TO_WINDOW_ARB,GL_TRUE,
		WGL_SUPPORT_OPENGL_ARB,GL_TRUE,
		WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
		WGL_COLOR_BITS_ARB,24,
		WGL_ALPHA_BITS_ARB,8,
		WGL_DEPTH_BITS_ARB,16,
		WGL_STENCIL_BITS_ARB,0,
		WGL_DOUBLE_BUFFER_ARB,GL_TRUE,
		WGL_SAMPLE_BUFFERS_ARB,GL_TRUE,
		WGL_SAMPLES_ARB,4,
		0,0
	};

	// First We Check To See If We Can Get A Pixel Format For 4 Samples
	valid = wglChoosePixelFormatARB(hDC,iAttributes,fAttributes,1,&pixelFormat,&numFormats);

	// If We Returned True, And Our Format Count Is Greater Than 1
	if (valid && numFormats >= 1)
	{
		arbMultisampleSupported = true;
		arbMultisampleFormat = pixelFormat;
		return arbMultisampleSupported;
	}

	// Our Pixel Format With 4 Samples Failed, Test For 2 Samples
	iAttributes[19] = 2;
	valid = wglChoosePixelFormatARB(hDC,iAttributes,fAttributes,1,&pixelFormat,&numFormats);
	if (valid && numFormats >= 1)
	{
		arbMultisampleSupported = true;
		arbMultisampleFormat = pixelFormat;
		return arbMultisampleSupported;
	}

	// Return The Valid Format
	return  arbMultisampleSupported;
}

/* This Code Creates Our OpenGL Window */
bool GL_Window::createWindowGL()
{
	//DWORD windowStyle = WS_OVERLAPPEDWINDOW;							// Define Our Window Style
	/* this style will prevent resizing and maximizing */
	DWORD windowStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
	DWORD windowExtendedStyle = WS_EX_APPWINDOW;						// Define The Window's Extended Style

	PIXELFORMATDESCRIPTOR pfd =											// pfd Tells Windows How We Want Things To Be
	{
		sizeof (PIXELFORMATDESCRIPTOR),									// Size Of This Pixel Format Descriptor
		1,																// Version Number
		PFD_DRAW_TO_WINDOW |											// Format Must Support Window
		PFD_SUPPORT_OPENGL |											// Format Must Support OpenGL
		PFD_DOUBLEBUFFER,												// Must Support Double Buffering
		PFD_TYPE_RGBA,													// Request An RGBA Format
		bitsPerPixel,													// Select Our Color Depth
		0, 0, 0, 0, 0, 0,												// Color Bits Ignored
		1,																// Alpha Buffer
		0,																// Shift Bit Ignored
		0,																// No Accumulation Buffer
		0, 0, 0, 0,														// Accumulation Bits Ignored
		16,																// 16Bit Z-Buffer (Depth Buffer)
		0,																// No Stencil Buffer
		0,																// No Auxiliary Buffer
		PFD_MAIN_PLANE,													// Main Drawing Layer
		0,																// Reserved
		0, 0, 0															// Layer Masks Ignored
	};

	RECT windowRect = {0, 0, width, height};							// Define Our Window Coordinates

	GLuint PixelFormat;													// Will Hold The Selected Pixel Format

	if ((fullScreen == true)  && ((firstRun == false) || multisample == false)) {											// Fullscreen Requested, Try Changing Video Modes
		if (changeScreenResolution (width, height, bitsPerPixel) == false) {
			/* Fullscreen Mode Failed.  Run In Windowed Mode Instead */
			MessageBox (HWND_DESKTOP, "Mode Switch Failed.\nRunning In Windowed Mode.", "Error", MB_OK | MB_ICONEXCLAMATION);
			AdjustWindowRectEx (&windowRect, windowStyle, 0, windowExtendedStyle);		// Adjust Window, Account For Window Borders
			fullScreen = false;										// Set isFullscreen To False (Windowed Mode)
		}
		else {															// Otherwise (If Fullscreen Mode Was Successful)
			ShowCursor (false);											// Turn Off The Cursor
			windowStyle = WS_POPUP;										// Set The WindowStyle To WS_POPUP (Popup Window)
			windowExtendedStyle |= WS_EX_TOPMOST;						// Set The Extended Window Style To WS_EX_TOPMOST
		}																// (Top Window Covering Everything Else)
	}
	else {																// If Fullscreen Was Not Selected
		// Adjust Window, Account For Window Borders
		AdjustWindowRectEx (&windowRect, windowStyle, 0, windowExtendedStyle);
	}

	// Create The OpenGL Window
	hWnd = CreateWindowEx (windowExtendedStyle,					// Extended Style
						   className,							// Class Name
						   title,								// Window Title
						   windowStyle,							// Window Style
						   0, 0,								// Window X,Y Position
						   windowRect.right - windowRect.left,	// Window Width
						   windowRect.bottom - windowRect.top,	// Window Height
						   HWND_DESKTOP,						// Desktop Is Window's Parent
						   0,									// No Menu
						   hInstance, 							// Pass The Window Instance
						   this);								// Pointer to this class, so WindowProc (main.cpp) can access this class

	if (hWnd == 0)	{											// Was Window Creation A Success?
		MessageBox (HWND_DESKTOP, "Error CreateWindowEx!", "Error", MB_OK | MB_ICONEXCLAMATION);
		return false;											// If Not Return False
	}

	hDC = GetDC (hWnd);									// Grab A Device Context For This Window
	if (hDC == 0)												// Did We Get A Device Context?
	{
			// Failed
		destroyWindowGL();
		MessageBox (HWND_DESKTOP, "Error GetDC!", "Error", MB_OK | MB_ICONEXCLAMATION);
		return false;													// Return False
	}

	/* Our first pass, Multisampling hasn't been created yet, so we create a window normally
	 * If it is supported, then we're on our second pass
	 * that means we want to use our pixel format for sampling
	 * so set PixelFormat to arbMultiSampleformat instead
	 */
	if (arbMultisampleSupported && multisample) {
		PixelFormat = arbMultisampleFormat;
	}
	else {
		PixelFormat = ChoosePixelFormat (hDC, &pfd);				// Find A Compatible Pixel Format
		if (PixelFormat == 0) {												// Did We Find A Compatible Format?
			// Failed
			destroyWindowGL();
			return false;
		}
	}

	if (SetPixelFormat (hDC, PixelFormat, &pfd) == false)		// Try To Set The Pixel Format
	{
		// Failed
		destroyWindowGL();
		MessageBox (HWND_DESKTOP, "Error SetPixelFormat!", "Error", MB_OK | MB_ICONEXCLAMATION);
		return false;													// Return False
	}

	hRC = wglCreateContext (hDC);						// Try To Get A Rendering Context
	if (hRC == 0)												// Did We Get A Rendering Context?
	{
		// Failed
		destroyWindowGL();
		MessageBox (HWND_DESKTOP, "Error wglCreateContext!", "Error", MB_OK | MB_ICONEXCLAMATION);
		return false;													// Return False
	}

	// Make The Rendering Context Our Current Rendering Context
	if (wglMakeCurrent (hDC, hRC) == false)
	{
		// Failed
		destroyWindowGL();
		MessageBox (HWND_DESKTOP, "Error wglMakeCurrent!", "Error", MB_OK | MB_ICONEXCLAMATION);
		return false;													// Return False

	}

	/*
	Now that our window is created, we want to queary what samples are available
	we call our InitMultiSample window
	if we return a valid context, we want to destroy our current window
	and create a new one using the multisample interface.
	*/
	if(firstRun && multisample) {
		firstRun = false;
		if(initMultisample()) {
			destroyWindowGL();
			return createWindowGL();
		}
	}

	ShowWindow (hWnd, SW_NORMAL);								// Make The Window Visible
	isVisible = true;											// Set isVisible To True

	reshapeGL (width, height);									// Reshape Our GL Window

	ZeroMemory (&keys, sizeof (Keys));							// Clear All Keys

	return true;														// Window Creating Was A Success
																		// Initialization Will Be Done In WM_CREATE
}

/* Loop though all messsages, block if window not visible (e.g. minimized) */
void GL_Window::processMessages()
{
	while (true) {
		/* Loop messages */
		while (PeekMessage (&msg, hWnd, 0, 0, PM_REMOVE) != 0)	{
				DispatchMessage (&msg);
		}
		if (isVisible == false)
			/* Block */
			WaitMessage ();
		else
			return;
	}
}

LRESULT CALLBACK GL_Window::windowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)														// Evaluate Window Message
	{
		case WM_CLOSE:													// Closing The Window
			terminate = true;									// Terminate The Application
		return 0;														// Return

		case WM_SIZE:													// Size Action Has Taken Place
			switch (wParam)												// Evaluate Size Action
			{
				case SIZE_MINIMIZED:									// Was Window Minimized?
					isVisible = false;									// Set isVisible To False
				return 0;												// Return

				case SIZE_MAXIMIZED:									// Was Window Maximized?
					isVisible = true;									// Set isVisible To True
					reshapeGL (LOWORD (lParam), HIWORD (lParam));		// Reshape Window - LoWord=Width, HiWord=Height
				return 0;												// Return

				case SIZE_RESTORED:										// Was Window Restored?
					isVisible = true;									// Set isVisible To True
					reshapeGL (LOWORD (lParam), HIWORD (lParam));		// Reshape Window - LoWord=Width, HiWord=Height
				return 0;												// Return
			}
		break;															// Break

		case WM_KEYDOWN:												// Update Keyboard Buffers For Keys Pressed
			if ((wParam >= 0) && (wParam <= 255))						// Is Key (wParam) In A Valid Range?
			{
				keys.keyDown [wParam] = true;							// Set The Selected Key (wParam) To True
				return 0;												// Return
			}
		break;															// Break

		case WM_KEYUP:													// Update Keyboard Buffers For Keys Released
			if ((wParam >= 0) && (wParam <= 255))						// Is Key (wParam) In A Valid Range?
			{
				keys.keyDown [wParam] = false;							// Set The Selected Key (wParam) To False
				return 0;												// Return
			}
		break;															// Break														// Break
	}

	return DefWindowProc (hWnd, uMsg, wParam, lParam);					// Pass Unhandled Messages To DefWindowProc
}

HDC GL_Window::getHDC()
{
	return hDC;
}
